<?php

namespace tmcore\services;

use tmcore\exceptions\ImportException;
use tmcore\traits\ErrorTrait;
use tmcore\utils\File;
use tmcore\utils\Helper;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use think\exception\ValidateException;
use think\facade\Filesystem;

class ImportExcelService
{
    use ErrorTrait;

    private static $instance = null;
    // 上传文件
    protected static $file;
    // 上传文件信息
    protected static array $fileInfo = [];
    // 上传文件最大字节
    protected static int $ruleSize = 10 * 1024 * 1024;
    // 上传文件后缀
    protected static string $ruleExt = 'xls,xlsx';
    // 保存文件目录
    protected static string $path = 'download/import/';
    // 保存模块
    protected static string $save_module = 'data';
    // 导入状态
    public static string $importStatus = 'success';
    // 表头信息
    public static array $fileHeader = [];
    // 添加数据
    public static array $createData = [];
    // 错误数据
    public static array $errorData = [];
    // 总条数
    public static int $totalCount = 0;
    // 失败条数
    public static int $failCount = 0;
    // 成功条数
    public static int $successCount = 0;

    // 初始化
    public function __construct()
    {

    }

    // 获取静态实例
    public static function instance()
    {
        if (self::$instance === null) self::$instance = new self();
        return self::$instance;
    }

    /**
     * 设置保存目录
     * @param $path
     * @param string $module
     * @return string
     */
    public function setPath($path, $module = '')
    {
        self::$path = $path;
        if($module)self::$save_module = $module;
        return self::$instance;
    }

    /**
     * 设置验证规则
     * @param int $size
     * @param string $ext
     * @return string
     */
    public function setRule($size = 0, $ext = '')
    {
        if($size)self::$ruleSize = $size;
        if($ext)self::$ruleExt = $ext;
        return self::$instance;
    }

    /**
     * 获取excel保存路径
     * @param bool $is_full
     * @return string
     */
    public static function getFileSavePath($is_full = false)
    {
        $root_path = app()->getRootPath();
        $time_dir = date('Ym') . '/' . date('d');

        $directory_path = self::$save_module . '/' . self::$path . $time_dir;
        if (!is_dir($root_path . $directory_path)) {
            if (File::mk_dir($root_path . $directory_path, 0700, true) == false) {
                return false;
            }
        }

        return ($is_full?$root_path . self::$save_module .'/':'') . self::$path . $time_dir;
    }

    /**
     * 获取文件信息
     * @return array
     */
    public function getFileInfo()
    {
        return self::$fileInfo;
    }

    /**
     * 验证器验证
     * @param string $name
     * @param array $rule
     * @param null $file
     * @throws ImportException
     */
    protected function validate($name, $rule, $file = null) {
        try {
            validate([$name => $this->extractValidate($rule)])->check([$name => $file?$file:self::$file]);
        } catch (ValidateException $e) {
            throw new ImportException($this->languageChange($e->getMessage()));
        }
    }

    /**
     * 英文转为中文
     * @param $msg
     * @return string
     */
    private function languageChange($msg)
    {
        $data = [
            // 上传错误信息
            'unknown upload error'                       => '未知上传错误！',
            'file write error'                           => '文件写入失败！',
            'upload temp dir not found'                  => '找不到临时文件夹！',
            'no file to uploaded'                        => '没有文件被上传！',
            'only the portion of file is uploaded'       => '文件只有部分被上传！',
            'upload File size exceeds the maximum value' => '上传文件大小超过了最大值！',
            'upload write error'                         => '文件上传保存错误！',
        ];

        return $data[$msg] ?? $msg;
    }

    /**
     * 提取上传验证
     * @param array $rule
     * @return string
     */
    private function extractValidate(array $rule)
    {
        $validate = [];
        foreach ($rule as $key => $value) {
            $validate[] = $key . ':' . (is_array($value) ? implode(',', $value) : $value);
        }
        return implode('|', $validate);
    }

    /**
     * 获取文件名称
     * @param string $ext
     * @return string
     */
    public static function getFileName($ext = '')
    {
        return date('YmdHis') . '_' . Helper::get_random(6, '0123456789') . '.' .$ext;
    }

    /**
     * 导入 excel
     * @param string $name
     * @param bool $is_delete
     * @return array|bool
     * @throws ImportException
     */
    public static function importExcel($name = "", $is_delete = true)
    {
        try{
            $file = request()->file($name) ?? null;
            if (empty($file)) {
                throw new ImportException('未找到上传文件的信息');
            }
            self::$file = $file;
            self::instance()->validate($name, ['fileSize' => self::$ruleSize, 'fileExt' => self::$ruleExt]);

            self::$fileInfo = [
                'ext'          => $file->extension(),
                'size'         => $file->getSize(),
                'originalName' => $file->getOriginalName(),
                'fileName'     => self::getFileName($file->extension())
            ];

            $path = self::getFileSavePath();
            $root_path = app()->getRootPath();

            // 将文件保存到本地
            $save_name = Filesystem::disk(self::$save_module)->putFileAs($path, $file, self::$fileInfo['fileName']);

            // 有Xls和Xlsx格式两种
            if (self::$fileInfo['ext'] == 'xlsx') {
                $objReader = IOFactory::createReader('Xlsx');
            } else {
                $objReader = IOFactory::createReader('Xls');
            }

            // 设置文件为只读
            $objReader->setReadDataOnly(TRUE);

            $file_path = $root_path . '/' . self::$save_module . '/' . $save_name;

            // 读取文件excel文件
            $objPHPExcel = $objReader->load($file_path);

            //excel中的第一张sheet
            $sheet = $objPHPExcel->getSheet(0);
            // 取得总行数
            $highestRow = $sheet->getHighestRow();
            // 取得总列数
            $highestColumn = $sheet->getHighestColumn();

            Coordinate::columnIndexFromString($highestColumn);
            $lines = $highestRow - 2;
            if ($lines <= 0) {
                throw new ImportException('数据不能为空');
            }
            // 直接取出excle中的数据
            $data = $objPHPExcel->getActiveSheet()->toArray();
            // 设置表头信息
            self::$fileHeader = $data[1];
            // 删除表头元素
            $data = array_splice($data, 2);
            if($is_delete)unlink($file_path);
            // 设置总条数
            self::$totalCount = count($data);
            // 返回结果
            return [
                'count' => count($data),
                'url' => self::$save_module . '/' . $save_name,
                'data' => $data
            ];
        } catch (\Exception $e) {
            throw new ImportException($e->getMessage(), $e->getCode());
        }
    }

    /**
     * 导入列表循环
     * @param array $list 循环数据
     * @param array $key_column_arr 字段对应数组
     * @param callable $callColumnFn 回调函数
     * @param callable $calFn 回调函数
     */
    public static function listForeach($list, $key_column_arr = [], $callColumnFn = null, $calFn = null) {
        foreach ($list as $key => $value) {
            try {
                foreach ($key_column_arr as $k => $v) {
                    // 字段检测回调函数
                    if($callColumnFn && is_callable($callColumnFn)) {
                        // 内容 行头字段名 表字段名 行数
                        call_user_func($callColumnFn, $value[$v], self::$fileHeader[$v], $k,  ($key + 2));
                    }
                }
                // 检测成功回调函数
                if($calFn && is_callable($calFn)) {
                    // 内容 行数
                    call_user_func($calFn, $value, ($key + 2));
                }
            } catch (ImportException $e) {
                continue;
            } catch (\Exception $e) {
                // 其他异常
                array_push(self::$errorData, ['key' => '系统错误', 'column_name' => '', 'row_count' => ($key + 2), 'status' => 'fail', 'reason' => $e->getMessage()]);
                ++self::$failCount;
                self::$importStatus = 'fail';
                continue;
            }
        }
    }

    /**
     * 创建excel示例文件
     * @param $header
     * @param $file_name
     * @return array
     */
    public static function createExcelTemplate($header, $file_name) {
        $instance = ExportExcelService::instance();
        $url = $instance
            ->setHeader($header,2)
            ->setTitle($file_name)
            ->createExcel($file_name, 'xlsx', app()->getRootPath() . 'public/uploads/excel');
        $file_name = $instance->getFileName();

        return ['url' => $url, 'file_name' => $file_name];
    }

    /**
     * 设置信息
     * @param string $key 行头字段名
     * @param string $column_name 表字段名
     * @param int $row_count 行数
     * @param string $reason 错误原因
     * @throws ImportException
     */
    public static function setError($key, $column_name, $row_count, $reason = '不符合要求') {
        $arr = ['key' => $key, 'column_name' => $column_name, 'row_count' => $row_count, 'status' => 'fail', 'reason' => $reason];
        array_push(self::$errorData, $arr);
        ++self::$failCount;
        self::$importStatus = 'fail';
        throw new ImportException($reason);
    }

    /**
     * 设置成功信息
     * @param $value
     */
    public static function setSuccess($value) {
        array_push(self::$createData, $value);
        ++self::$successCount;
    }

    /**
     * 获取导入结果
     * @return array
     */
    public static function getImportResult() {
        return [
            'status' => self::$importStatus,
            'total_count' => self::$totalCount,
            'fail_count' => self::$failCount,
            'success_count' => self::$successCount,
            'error_data' => self::$errorData,
            'create_data' => self::$createData
        ];
    }
}